﻿/* Copyright (c) 2010 EPiServer AB
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
 * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace EPiServer.Municipality.Web {
    public class StrictFilter : Stream{
        Stream responseStream;
        private readonly StreamWriter _streamWriter;
        private readonly StringBuilder _responseHtml = new StringBuilder();
        private readonly Encoding _encoding = new UTF8Encoding();
        private bool _isClosed;

        /* *******************************************************************
         *  Properties 
         * *******************************************************************/
        #region public override bool CanRead
        /// <summary>
        /// Gets the CanRead of the PageFilter
        /// </summary>
        /// <value></value>
        public override bool CanRead {
            get {
                return false;
            }
        }
        #endregion
        #region public override bool CanSeek
        /// <summary>
        /// Gets the CanSeek of the PageFilter
        /// </summary>
        /// <value></value>
        public override bool CanSeek {
            get {
                return false;
            }
        }
        #endregion
        #region public override bool CanWrite
        /// <summary>
        /// Gets the CanWrite of the PageFilter
        /// </summary>
        /// <value></value>
        public override bool CanWrite {
            get {
                return responseStream.CanWrite;
            }
        }
        #endregion
        #region public override long Length
        /// <summary>
        /// Gets the Length of the PageFilter
        /// </summary>
        /// <value></value>
        public override long Length {
            get {
                throw new NotSupportedException();
            }
        }
        #endregion
        #region public override long Position
        /// <summary>
        /// Get/Sets the Position of the PageFilter
        /// </summary>
        /// <value></value>
        public override long Position {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }
        #endregion

        /* *******************************************************************
         *  Methods 
         * *******************************************************************/
        #region public StrictFilter(Stream inputStream, Encoding encoding)
        /// <summary>
        /// Initializes a new instance of the <b>StrictFilter</b> class.
        /// </summary>
        /// <param name="inputStream"></param>
        /// <param name="encoding"></param>
        public StrictFilter(Stream inputStream, Encoding encoding) {
            responseStream = inputStream;
            _encoding = encoding;
            _streamWriter = new StreamWriter(inputStream, encoding);
        }
        #endregion
        #region public override void Close()
        /// <summary>
        /// 
        /// </summary>
        public override void Close() {
            if (_isClosed)
                return;

            Flush();
            Flush(false);
            _streamWriter.Close();
            _isClosed = true;
        }
        #endregion
        #region public override void Flush()
        /// <summary>
        /// 
        /// </summary>
        public override void Flush() {
            if (_isClosed)
                return;

            Flush(true);
        }
        #endregion
        #region private void Flush(bool parseData)
        /// <summary>
        /// Flushes the stream out to underlying storage
        /// </summary>
        /// <param name="parseData">Should we parse for data?</param>
        private void Flush(bool parseData) {
            if (_responseHtml == null || _responseHtml.Length == 0)
                return;

            string data = _responseHtml.ToString();
            int length = data.Length;
            _responseHtml.Remove(0, length);
            if (parseData) {
                data = ParseText(data);
            }
            _streamWriter.Write(data);
            _streamWriter.Flush();
        }
        #endregion
        #region public override long Seek(long offset, SeekOrigin origin)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="offset"></param>
        /// <param name="origin"></param>
        /// <returns></returns>
        public override long Seek(long offset, SeekOrigin origin) {
            throw new NotSupportedException();
        }
        #endregion
        #region public override void SetLength(long length)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="length"></param>
        public override void SetLength(long length) {
            throw new NotSupportedException();
        }
        #endregion
        #region public override int Read(byte[] buffer, int offset, int count)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public override int Read(byte[] buffer, int offset, int count) {
            throw new NotSupportedException();
        }
        #endregion
        #region public override void Write(byte[] buffer, int offset, int count)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        public override void Write(byte[] buffer, int offset, int count) {
            if (_isClosed)
                return;

            string strBuffer = _encoding.GetString(buffer, offset, count);
            _responseHtml.Append(strBuffer);
        }
        #endregion
        #region public static string ParseText(string finalHtml)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="finalHtml"></param>
        /// <returns></returns>
        public static string ParseText(string finalHtml) {
            
            // Remove the "name" attribute from <form> tags because they are invalid
            var re = new Regex("<form\\s+(name=.*?\\s)", RegexOptions.IgnoreCase);
            finalHtml = re.Replace(finalHtml, new MatchEvaluator(FormNameMatch));

            // Remove target="_blank" or any other target and replace it with popup class	
            re = new Regex("(?<=\\<a\\b)([^>]*)", RegexOptions.IgnoreCase);
            finalHtml = re.Replace(finalHtml, new MatchEvaluator(TargetMatch));

            return finalHtml;
        }
        #endregion
        #region private static string FormNameMatch (Match m)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        private static string FormNameMatch(Match m) {
            return m.ToString().Replace(m.Groups[1].Value, string.Empty);
        }
        #endregion
        #region private static string TargetMatch(Match m)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        private static string TargetMatch(Match m) {
            string txt = m.Groups[1].Value;
            Match targetMatch = Regex.Match(txt, @"\s*\btarget\s*=\s*(""[^""]+""|'[^']+'|[^\s]+)", RegexOptions.IgnoreCase);
            string css;
            if (targetMatch.Success) {
                txt = txt.Substring(0, targetMatch.Index) + txt.Substring(targetMatch.Index + targetMatch.Length);

                Match cssMatch = Regex.Match(txt, @"\bclass\s*=\s*(""[^""]+""|'[^']+'|[^\s]+)", RegexOptions.IgnoreCase);
                css = cssMatch.Groups[1].Value;
                char[] charsToTrim = { '"' };
                css = css.Trim(charsToTrim);

                return " class=\"" + css + " popup\"" + txt;
            }
            return txt;
        }
        #endregion
    }
}
